﻿using DBHelper;
using Model;
using HslCommunication;
using HslCommunication.Profinet.Siemens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.ComponentModel;
using Comm;
using System.Net;
namespace Server
{

    public class PlcHelper
    {
        /// <summary>
        /// 向外推送标签数值事件
        /// </summary>
        public static event Action<string, int> PushTagValEvent;
        /// <summary>
        /// 通知外部  PLC通信状态     做PLC断线发布，para1是PLC编号，para2是通信成功与否
        /// </summary>
        public static event Action<string, bool> MsgConnectionStatus;
        /// <summary>
        /// 向外推送错误信息
        /// </summary>
        public static event Action<string> MsgErrEvent;
        /// <summary>
        /// PLC字典——根据数据库中PLC表动态生成
        /// </summary>
        private static Dictionary<string, SiemensS7Net> PlcDic = new Dictionary<string, SiemensS7Net>();
        /// <summary>
        /// PLC变量字典——和PLC字典关联——通过PLCName关联
        /// </summary>
        private static Dictionary<string, t_PlcAddr> PlcAddrDic = new Dictionary<string, t_PlcAddr>();
        /// <summary>
        /// PLC采集采集线程集合——通过PLC表动态生成
        /// </summary>
        private static List<Thread> MonitorThreads;

        private static Dictionary<string, bool> PlcStatus = new Dictionary<string, bool>();



        /// <summary>
        /// 初始化PLC
        /// </summary>
        /// <returns></returns>
        public static void InitPlc()
        {
            #region 初始化PLC地址字典
            {
                PlcAddrDic.Clear();
                List<t_PlcAddr> list = DbContext.GetModels<t_PlcAddr>("1=1");
                if (list == null || list.Count < 1)
                {
                    //todo 没有查询到PLC地址结果集合 这里要进行日志记录
                    return;
                }
                list.ForEach(p =>
                    {
                        PlcAddrDic[p.NO] = p;
                        p.PropertyChanged += PlcAddr_PropertyChanged;
                    });
            }

            #endregion

            #region 初始化PLC
            //拿到PLC表  实体集合
            List<t_Plc> plcs = new List<t_Plc>();

            //遍历地址标签集合拿到所有的PLC信息
            foreach (var item in PlcAddrDic.Values)
            {
                if (plcs.Count(p => p.NO.Trim() == item.PlcNo.Trim()) == 0)
                {
                    //从PLC表中获取对应的PLC的具体信息
                    t_Plc plcTemp = DbContext.GetModel<t_Plc>($"No='{item.PlcNo}'");
                    if (plcTemp == null)
                    {
                        //MyLog.WriteLog("PLC地址关联到未存在的PLC****检查PLC地址表");
                        continue;
                    }
                    plcs.Add(plcTemp);
                }
            }
            //先把所有的PLC链接停止
            if (PlcDic.Count > 0)
            {
                foreach (var item in PlcDic.Values)
                {
                    item?.ConnectClose();
                }
            }
            PlcDic.Clear(); //清空PLC字典

            if (plcs == null || plcs.Count < 1)
            {
                Msg("未检索到PLC信息，PLC字典空");
                //MyLog.WriteLog("未检索到PLC信息");
                return;
            }
            //PLC配置信息集合建立OK，加入到PLC字典中
            plcs.ForEach(p =>
            {
                try
                {
                    PlcDic.Add(p.NO, new SiemensS7Net(SiemensPLCS.S1200, "127.0.0.1"));
                    PlcStatus.Add(p.NO, false);
                }
                catch (Exception ex)
                {
                    Msg("PLC创建失败");
                    //MyLog.WriteLog("PLC创建失败");
                    return;
                }
            });
            #endregion

            Start();
        }

        /// <summary>
        /// 向外推送Plc变量信息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void PlcAddr_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var addr = sender as t_PlcAddr;
            if (addr == null || e.PropertyName != "AddrValue") return;

            //if (addr.Enable == false) return;
            if (PushTagValEvent == null)
            {
                return;
            }
            var methodInfo = PushTagValEvent.GetInvocationList();

            foreach (var @delegate in methodInfo)
            {
                var method = (Action<string, int>)@delegate;
                method.BeginInvoke(addr.Name, addr.AddrValue, null, null);
            }
        }
        /// <summary>
        /// 开始读取PLC的变量
        /// </summary>
        /// <returns></returns>
        private static bool Start()
        {
            if (MonitorThreads != null && MonitorThreads.Count > 0)
            {
                //说明这是非第一次初始化——先把以前的线程停止
                foreach (var item in MonitorThreads)
                {
                    item.Abort();
                    item.DisableComObjectEagerCleanup();
                }
            }

            MonitorThreads = new List<Thread>();

            foreach (var item in PlcDic)
            {
                MonitorThreads.Add(new Thread(new ThreadStart(() => { MonitorPlc(item.Key); })));
            }

            //把线程库启动
            foreach (var item in MonitorThreads)
            {
                item.IsBackground = true;
                item.Start();
            }
            return true;
        }
        /// <summary>
        /// 轮询PLC变量方法
        /// </summary>
        /// <param name="plcNO">plcNO</param>
        private static void MonitorPlc(string plcNO)
        {
            //连接PLC
            var rlt = PlcDic[plcNO].ConnectServer();
            while (!rlt.IsSuccess)
            {
                Thread.Sleep(1000);
                Msg($"连接PLC-{plcNO}失败，1s后重试！");
                rlt = PlcDic[plcNO].ConnectServer();
            }
            int[] addr = GetMinMaxAddr(plcNO, out string minadr);
            OperateResult<byte[]> result;
            while (true)
            {
                lock (PlcDic[plcNO])
                {
                    result = PlcDic[plcNO].Read(minadr, (ushort)addr[1]);
                }

                if (result.IsSuccess)
                {
                    foreach (var item in PlcAddrDic)
                    {
                        if (item.Value.PlcNo == plcNO && item.Value.IsPara == false)
                        {
                            //三菱协议按照   字进行读取
                            // item.Value.AddrValue = result.Content[Convert.ToInt16(item.Value.Address.Split('.')[1]) - addr[0]]; //地址前两位是D，要去掉//
                            item.Value.AddrValue = PlcDic[plcNO].ByteTransform.TransInt16(result.Content,
                                Convert.ToInt16(item.Value.Address.Split('.')[1]) - addr[0]);
                        }
                    }
                    MsgConnectionStatus?.Invoke(plcNO, true);
                    PlcStatus[plcNO] = true;
                }
                else
                {
                    MsgConnectionStatus?.Invoke(plcNO, false);
                    PlcStatus[plcNO] = false;
                }
                Thread.Sleep(500);
            }
        }
        /// <summary>
        /// PLC 写变量方法
        /// </summary>
        /// <param name="addr">地址信息，给定ID或者名字可以——暂定给定名称吧</param>
        /// <param name="val">要设定的值</param>
        /// <returns></returns>
        public static OperateResult SetTag(string name, short val)
        {
            //首先看看变量是否在地址表中定义
            var Tag = PlcAddrDic.Values.FirstOrDefault(p => p.Name == name);
            if (Tag == null || Tag.ID < 0)
            {
                Msg($"要写的变量{name}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<bool>(new OperateResult("要写的变量信息未再交互系统中定义"));
            }
            //再拿到变量绑定的PLC的信息
            if (!PlcDic.ContainsKey(Tag.PlcNo))
            {
                Msg("要写的变量未绑定PLC，请检查");
                Logger.WritePlcLog($"要写的变量{name}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<bool>(new OperateResult("要写的变量未绑定PLC，请检查"));
            }

            var plc = PlcDic[Tag.PlcNo];
            OperateResult result;
            //添加锁，一个PLC   只能有一个写工作
            lock (plc)
            {
                result = plc.Write(Tag.Address.Remove(1, 1), val);
            }
            if (result.IsSuccess)
            {
                return OperateResult.CreateSuccessResult();
            }
            else
            {
                string msg = $"写失败【{Tag.Name}】数值【{val}】,错误信息【{result.Message}】";
                Msg(msg);
                Logger.WritePlcLog(msg);
                return result;
            }

        }

        private static void Msg(string msg)
        {
            try
            {
                MsgErrEvent?.Invoke(msg);
            }
            catch (Exception e)
            {
                Logger.WritePlcLog(e.Message, e);
            }
        }
        /// <summary>
        /// 拿到地址中最大和最小的地址
        /// </summary>
        /// <param name="plc">要计算地址的PLC</param>
        /// <returns>返回int[]，其中int[0]时最小地址，int[1]为数据长度</returns>
        private static int[] GetMinMaxAddr(string plcNO, out string minAddrstr)
        {
            int[] val = { 9999, -1 };//[0]=min，[1]=length
            string adr = "";
            int maxAddr = PlcAddrDic.Values.Max(p =>
            {
                if (p.PlcNo == plcNO && !p.IsPara)
                {


                    return Convert.ToInt32(p.Address.Split('.')[1]);
                }
                return 0;
            });

            int minAddr = PlcAddrDic.Values.Min(p =>
            {
                if (p.PlcNo == plcNO && !p.IsPara)
                {
                    adr = p.Address;
                    return Convert.ToInt32(p.Address.Split('.')[1]);
                }
                return 99999;
            });
            minAddrstr = PlcAddrDic.Values.FirstOrDefault().Address.Split('.')[0] + '.' + minAddr.ToString();
            val[0] = minAddr;
            //西门子协议   按照字节读取，读取的地址要加2
            val[1] = maxAddr - minAddr + 2;
            return val;
        }
        /// <summary>
        /// 根据标签的NO拿到地址的数值
        /// </summary>
        /// <param name="tagNo"></param>
        /// <returns></returns>
        public static OperateResult<int> GetTagVal(string tagNo)
        {
            if (!PlcAddrDic.ContainsKey(tagNo))
            {
                return OperateResult.CreateFailedResult<int>(new OperateResult("地址列表中不包含这个地址"));
            }
            string NO = "";
            NO = PlcAddrDic[tagNo].PlcNo;
            if (!PlcStatus[NO])
            {
                var result = OperateResult.CreateFailedResult<int>(new OperateResult());
                result.Message = "这个地址标签的PLC通信状态失败";
                return result;
            }
            return OperateResult.CreateSuccessResult<int>(PlcAddrDic[tagNo].AddrValue);
        }
        /// <summary>
        /// 拿到PLC地址集合
        /// </summary>
        /// <returns></returns>
        public static List<t_PlcAddr> GetPlcAddrs()
        {
            return PlcAddrDic.Values.ToList();
        }
        /// <summary>
        /// 获取PLC最新数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tagNo"></param>
        /// <returns></returns>
        public static OperateResult<string> GetValInTime(string name, ushort length)
        {
            var Tag = PlcAddrDic.Values.FirstOrDefault(p => p.Name == name);
            if (Tag == null || Tag.ID < 0)
            {
                Msg($"要读取变量{name}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<string>(new OperateResult("要读取变量信息未再交互系统中定义"));
            }
            //再拿到变量绑定的PLC的信息
            if (!PlcDic.ContainsKey(Tag.PlcNo))
            {
                Msg("要读取的变量未绑定PLC，请检查");
                Logger.WritePlcLog($"要读取变量{name}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<string>(new OperateResult("要读取变量未绑定PLC，请检查"));
            }

            var plc = PlcDic[Tag.PlcNo];
            OperateResult<string> result;

            lock (plc)
            {
                result = plc.ReadString(Tag.Address, length);
            }
            if (result.IsSuccess)
            {
                return OperateResult.CreateSuccessResult(result.Content);
            }
            else
            {
                string msg = $"读取失败【{Tag.Name}】,错误信息【{result.Message}】";
                Msg(msg);
                Logger.WritePlcLog(msg);
                return OperateResult.CreateFailedResult<string>(result);
            }
        }

        public static OperateResult<byte[]> GetValBytes(string name, ushort length)
        {
            var Tag = PlcAddrDic.Values.FirstOrDefault(p => p.Name == name);
            if (Tag == null || Tag.ID < 0)
            {
                Msg($"要读取变量{name}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<byte[]>(new OperateResult("要读取变量信息未再交互系统中定义"));
            }
            //再拿到变量绑定的PLC的信息
            if (!PlcDic.ContainsKey(Tag.PlcNo))
            {
                Msg("要读取的变量未绑定PLC，请检查");
                Logger.WritePlcLog($"要读取变量{name}未绑定PLC，请检查");
                return OperateResult.CreateFailedResult<byte[]>(new OperateResult("要读取变量未绑定PLC，请检查"));
            }

            var plc = PlcDic[Tag.PlcNo];
            OperateResult<byte[]> result;

            lock (plc)
            {
                result = plc.Read(Tag.Address, length);
            }
            if (result.IsSuccess)
            {
                return OperateResult.CreateSuccessResult(result.Content);
            }
            else
            {
                string msg = $"读取失败【{Tag.Name}】,错误信息【{result.Message}】";
                Msg(msg);
                Logger.WritePlcLog(msg);
                return OperateResult.CreateFailedResult<byte[]>(result);
            }
        }
    }
}
